/* $Id$ */

// SpewView is written by Pedja Bogdanovich
// Permission is granted to freely use and distribute this software
// provided this notice is left attached and no monetary gain is made.

#import "SpewView.h"
#import "Thinker.h"

#define TEXTTIMEDEFAULT "Spew TextTime"
#define SLEEPTIMEDEFAULT "Spew SleepTime"

#define MINWIDTHRAT .2
#define MAXWIDTHRAT .75
#define MINCHARS 12
#define MAXFONTRAT .075
#define MINFONTRAT .025
#define MINFONTSIZE 12

#define exp2(x) pow(2,x)
#define rnd() ((random()&0xffffff)/(float)0xffffff)

static const char *fontlist[]={
  "Courier", "Courier-Oblique", "Courier-Bold", "Courier-BoldOblique",
  "Helvetica", "Helvetica-Oblique", "Helvetica-Bold", "Helvetica-BoldOblique",
  "Times-Roman", "Times-Italic", "Times-Bold", "Times-BoldItalic"
  // Add more fonts here...
  };

static int pointsize[]={
  14,18,24,32,36,48,56,64
  // Add more font-sizes here...
  };

static NXColor pickColor()
{
  int i;
  float r, g, b;
  switch([Window defaultDepthLimit]) {
  case NX_TwoBitGrayDepth:
    return NXConvertGrayToColor(1.-(random()%2)/3.);
  case NX_EightBitGrayDepth:
    return NXConvertGrayToColor(1.-(random()%128)/256.);
  case NX_TwelveBitRGBDepth:
  case NX_TwentyFourBitRGBDepth:
  default:
    i=random()%4096;
    r=fmod(i,16)/16;
    g=fmod(i-r,256)/256;
    b=(i-fmod(i,256))/4096;
    r=.2+r*.8; g=.2+g*.8; b=.2+b*.8; /* Not too dark. */
    return NXConvertRGBToColor(r,g,b);
  }
}

// fontsizes can be made font specific...
static float pickFontSize(float fmin, float fmax)
{
  int i, j=sizeof(pointsize)/sizeof(int);
  int imin=j-1, imax=0;
  for(i=0;i<j;i++) if(pointsize[i]>=fmin) { imin=i; break; }
  for(i=0;i<j;i++) if(pointsize[i]<=fmax) imax=i;
  if(imin>=imax) i=imin;
  else i=imin+random()%(imax-imin+1);
  return pointsize[i];
}

static Font *pickFont(float sz)
{
  return [Font newFont:fontlist[random()%(sizeof(fontlist)/sizeof(char*))]
		 size:sz /* matrix:NX_IDENTITYMATRIX*/];
}

@implementation SpewView
+ initialize
{
  static NXDefaultsVector spewDefaults={
    {TEXTTIMEDEFAULT,"12"},
    {SLEEPTIMEDEFAULT,"2"},
    {NULL}};
  NXRegisterDefaults([NXApp appName],spewDefaults);
  return [super initialize];
}

- initFrame:(const NXRect *)fr
{
  NXSize sz={1e6,1e6};

  [super initFrame:fr];
  textOnScreen=NO;

  textView=[[Text alloc] initFrame:NULL];
//  [textView setRetainedWhileDrawing:NO];
  [textView setVertResizable:YES];
  [[textView setHorizResizable:NO] setHorizResizable:NO];
  [[textView setSelectable:YES] setEditable:NO];
  [textView setMonoFont:YES];
  [textView setBackgroundGray:NX_BLACK];
  [textView setMaxSize:&sz];

  defaultTextTime=atoi(NXGetDefaultValue([NXApp appName],TEXTTIMEDEFAULT));
  defaultSleepTime=atoi(NXGetDefaultValue([NXApp appName],SLEEPTIMEDEFAULT));
  srandom(time(0));
  return self;
}

// spewAgain method will load new spew text into textView.
// textView's text content, font, color, and frame are set.
- spewAgain
{
  char buf[200];
  char tmpfile[100];
  FILE *f;
  NXRect r;
  static const int alignment[]={NX_LEFTALIGNED,NX_RIGHTALIGNED,NX_CENTERED};

  sprintf(tmpfile,"/tmp/.#tmp_spew%08x",getpid());
  sprintf(buf,"sh -c 'cd %s; spew >%s'",
	  [(BSThinker()) moduleDirectory:"Spew"],
	  tmpfile);
  system(buf);

  if(f=fopen(tmpfile,"r")) {
    struct stat statbuf;
    char *spewtext;
    float w, h, x, y;

    fstat(fileno(f),&statbuf);
    spewtext=alloca(statbuf.st_size+1);
    fread(spewtext,1,statbuf.st_size,f); spewtext[statbuf.st_size]=0;
    fclose(f);

    // Random selections below don't work very well for small windows...

    // set color
    [textView setTextColor:pickColor()];

    // set font
    h=pickFontSize(MAX(MINFONTSIZE,NX_HEIGHT(&bounds)*MINFONTRAT), /* min */
		   NX_HEIGHT(&bounds)*MAXFONTRAT); /* max font size */
    [textView setFont:pickFont(h)];

    // set width
    w=MAX(NX_WIDTH(&bounds)*MINWIDTHRAT,MINCHARS*[[textView font] pointSize]);
    x=NX_WIDTH(&bounds)*MAXWIDTHRAT;
    if(w<x) w+=(x-w)*rnd();
    [textView sizeTo:w :50];

    // set content
    [textView setSel:0 :[textView textLength]];
    [textView replaceSel:spewtext];

    // set alignment
    [textView setAlignment:alignment[random()%(sizeof(alignment)/sizeof(int))]];

    // set position; height is calculated by the text object
    [textView getFrame:&r];
    w=NX_WIDTH(&r); h=NX_HEIGHT(&r);
    x=NX_X(&bounds)+(NX_WIDTH(&bounds)-w)*rnd();
    y=NX_Y(&bounds)+(NX_HEIGHT(&bounds)-h)*rnd();
    [textView moveTo:x :y];
  }  
  unlink(tmpfile);
  return self;
}

- drawSelf:(NXRect *)rects :(int)rectCount
{
  if(rects==NULL || rectCount==0) return self;
  PSsetgray(NX_BLACK); NXRectFill(rects);
  return self;
}

- inspector:sender
{
  char buf[MAXPATHLEN];
	
  if(!sharedInspectorPanel) {
    sprintf(buf,"%s/Spew.nib",[sender moduleDirectory:"Spew"]);
    [NXApp loadNibFile:buf owner:self withNames:NO];

    // load default values into inspector
    [textTimeField setIntValue:defaultTextTime];
    [textTimeField sendAction:[textTimeField action] to:[textTimeField target]];
    [sleepTimeField setIntValue:defaultSleepTime];
    [sleepTimeField sendAction:[sleepTimeField action] to:[sleepTimeField target]];
  }
  return sharedInspectorPanel;
}

// This method is stolen from BackView
- (BOOL)timePassed:(BStimeval)delay
{
  BStimeval now, msec;
  BOOL result;
	
  now=currentTimeInMs();
  if(SVthen==0) SVthen=now;	/* added by shou-h@nexus.or.jp */
  msec=now-SVthen;

  /* so as not to suck too many cycles, if I'm waiting for some
     time more than a tenth of a second in the future, I sleep
     a while.  This interval is short enough that the app shouldn't
     seem unresponsive to user actions.
	
     ok, so you'd never pull this trick if the user had to type.
     A better solution would be to coordinate the timed entry better,
     but I get slightly better performance from spinning in my
     timed entry (a bad idea for most apps...) */
	
  if((msec+120)<delay) {
    usleep(110000);
    return NO;
  }
	
  result=(msec>delay);
  if(result) SVthen=now;

  return result;
}


- oneStep
{
  int t; char buf[12];

  if(sharedInspectorPanel) {
    t=[textTimeField intValue];
    if(t!=defaultTextTime) {	/* update default texttime if needed */
      sprintf(buf,"%d",t); defaultTextTime=t;
      NXWriteDefault([NXApp appName],TEXTTIMEDEFAULT,buf);
    }
    t=[sleepTimeField intValue];
    if(t!=defaultSleepTime) {	/* update default sleeptime if needed */
      sprintf(buf,"%d",t); defaultSleepTime=t;
      NXWriteDefault([NXApp appName],SLEEPTIMEDEFAULT,buf);
    }
  }    

  if(textOnScreen) {
    if(![self timePassed:defaultTextTime*1000]) return self;
    [textView removeFromSuperview];
  } else {
    if(![self timePassed:defaultSleepTime*1000]) return self;
    [self spewAgain];
    [self addSubview:textView];
    [window makeFirstResponder:textView];
  }
  textOnScreen=!textOnScreen;
  [self display];
  return self;
}
@end

/* EOF */